/***
 * Copyright (c) 2021-2031 murenchao
 * fig is licensed under Mulan PubL v2.
 * You can use this software according to the terms and conditions of the Mulan PubL v2.
 * You may obtain a copy of Mulan PubL v2 at:
 *       http://license.coscl.org.cn/MulanPubL-2.0
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PubL v2 for more details.
 */
package cool.taomu.software.fig.rpc

import com.google.gson.Gson
import cool.taomu.software.fig.rpc.entity.RpcEntity
import cool.taomu.software.fig.rpc.entity.RpcResultEntity
import cool.taomu.software.fig.rpc.inter.Callback
import io.netty.bootstrap.Bootstrap
import io.netty.channel.ChannelHandlerContext
import io.netty.channel.ChannelInboundHandlerAdapter
import io.netty.channel.ChannelInitializer
import io.netty.channel.ChannelOption
import io.netty.channel.nio.NioEventLoopGroup
import io.netty.channel.socket.SocketChannel
import io.netty.channel.socket.nio.NioSocketChannel
import io.netty.handler.codec.LengthFieldBasedFrameDecoder
import io.netty.handler.codec.LengthFieldPrepender
import io.netty.handler.codec.string.StringDecoder
import io.netty.handler.codec.string.StringEncoder
import io.netty.util.CharsetUtil
import org.eclipse.xtend.lib.annotations.Accessors

@Accessors
class RpcClient {
    val clientGroup = new NioEventLoopGroup(1);
    Bootstrap boot;
    String ip;
    Integer port;

    def init(Callback<?> callback) {
        boot = new Bootstrap();
        boot.group(clientGroup).channel(NioSocketChannel)
        boot.option(ChannelOption.TCP_NODELAY, true);
        boot.handler(new ChannelInitializer<SocketChannel>() {
            override protected initChannel(SocketChannel ch) throws Exception {
                var pipeline = ch.pipeline();
                pipeline.addLast("frameDecoder", new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));
                pipeline.addLast("frameEncoder", new LengthFieldPrepender(4));
                pipeline.addLast("decoder", new StringDecoder(CharsetUtil.UTF_8));
                pipeline.addLast("encoder", new StringEncoder(CharsetUtil.UTF_8));
                pipeline.addLast("handler", new ChannelInboundHandlerAdapter() {
                    override channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                        var result = new Gson().fromJson(msg as String, RpcResultEntity);
                        callback.call(result);
                    }

                    override exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
                        cause.printStackTrace();
                        ctx.close();
                    }
                });
            }
        });
    }

    def send(Class<?> zlass, String methodName, Object[] args) {
        try {
            var iTaskMethod = zlass.declaredMethods.findFirst[m|m.name == methodName]
            var rpc = new RpcEntity(zlass, methodName, iTaskMethod.returnType, iTaskMethod.parameterTypes, args);
            var f = boot.connect(this.ip, this.port).sync();
            var result = new Gson().toJson(rpc);
            f.channel().writeAndFlush(result);
            f.channel().closeFuture().sync()
        } finally {
            clientGroup.shutdownGracefully();
        }
    }
}
